﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using HgRemoteContracts;
using System.Text.RegularExpressions;
using System.IO;
using System.Diagnostics;
using System.Threading;
using System.ServiceModel;

namespace HgRemote
{
	public partial class AppWin : Form
	{
		private const RegexOptions ro = RegexOptions.Compiled | RegexOptions.Singleline;
		private static Regex regexDiffLineNums = new Regex(@"^@@ -\d+,\d+ \+\d+,\d+ @@$", ro);
		private static Regex regexDiffAdds = new Regex(@"^\+.*", ro);
		private static Regex regexDiffRemoves = new Regex(@"^-.*", ro);
		private static Regex regexDiffHeaders = new Regex(@"(---|\+\+\+) .*", ro);

		private HgRepo currentRepo;
		private List<DisplayStatusItem> items = new List<DisplayStatusItem>();

		public AppWin()
		{
			InitializeComponent();
		}

		#region AppWin_Load

		private void AppWin_Load(object sender, EventArgs args)
		{
			Application.ThreadException += (s1, e1) => HandleError(e1.Exception);
			AppDomain.CurrentDomain.UnhandledException += (s2, e2) => HandleError(e2.ExceptionObject as Exception);

			tsTasks.Visible = false;

			Settings s = Settings.Current;

			// Do purely visual stuff first
			if (s.User != null)
			{
				this.SuspendLayout();

				if (s.WindowState == FormWindowState.Maximized)
					this.WindowState = FormWindowState.Maximized;
				else
				{
					this.Size = s.WindowSize;
					this.Location = s.WindowLocation;
					this.WindowState = s.WindowState;
				}

				this.ResumeLayout();

				if (s.HorizontalSplitDistance >= 0)
					SplitCommit.SplitterDistance = s.HorizontalSplitDistance;
				if (s.VerticalSplitDistance >= 0)
					SplitStatus.SplitterDistance = s.VerticalSplitDistance;
			}
			else
			{
				SettingsDialog sd = new SettingsDialog();
				DialogResult dr = sd.ShowDialog(this);

				if (s.User == null)
					Application.Exit();
			}

			try
			{
				// Load repositories
				List<HgRepo> repos = Client.Current.GetRepositories();
				RepositorySelect.Items.AddRange(repos.ToArray());

				// Repository dependent settings
				if (!String.IsNullOrEmpty(s.SelectedRepo))
					RepositorySelect.SelectedItem = repos.FirstOrDefault(r => r.Name == s.SelectedRepo);
			}
			catch (Exception x)
			{
				HandleError(x);
			}
		} 

		#endregion

		private void HandleError(Exception x)
		{
			MessageBox.Show(this, x.Message + x.StackTrace, x.GetType().Name);
		}

		private void OnRepositorySelected(object sender, EventArgs e)
		{
			HgRepo repo = RepositorySelect.SelectedItem as HgRepo;
			if (repo != null)
			{
				currentRepo = repo;
				tsTasks.DropDownItems.Clear();
				if (currentRepo.Tasks != null && currentRepo.Tasks.Count > 0)
				{
					tsTasks.Visible = true;
					tsTasksSep.Visible = true;
					foreach (HgTask task in currentRepo.Tasks)
					{
						ToolStripMenuItem mi = new ToolStripMenuItem(task.Name);
						mi.Tag = task;
						mi.Click += new EventHandler(OnRepoTaskMenuItemClick);
						tsTasks.DropDownItems.Add(mi);
					}
				}
				else
				{
					tsTasks.Visible = false;
					tsTasksSep.Visible = false;
				}
				GetStatus();
			}
		}

		void OnRepoTaskMenuItemClick(object sender, EventArgs e)
		{
			try
			{
				HgTask task = (sender as ToolStripMenuItem).Tag as HgTask;
				CmdResultDialog rd = new CmdResultDialog();
				Client.SetResponseTarget(rd);
				Client.Current.RunTask(currentRepo.Name, task.Name);
				rd.ShowDialog();
			}
			catch (Exception x)
			{
				HandleError(x);
			}

		}

		#region GetStatus

		private void GetStatus()
		{
			if (currentRepo == null)
			{
				MessageBox.Show("Select a repository.");
				return;
			}

			try
			{
				List<StatusItem> statuses = Client.Current.Status(currentRepo.Name);
				Rebind(statuses);
			}
			catch (Exception x)
			{
				HandleError(x);
			}
		} 

		#endregion

		#region Rebind

		private void Rebind(List<StatusItem> wireStatuses)
		{
			List<DisplayStatusItem> statuses = wireStatuses.ConvertAll(ws => new DisplayStatusItem(ws));
			StatusItemGrid.AutoGenerateColumns = false;
			StatusItemGrid.DataSource = statuses;
			items = statuses;

			foreach (DataGridViewRow row in StatusItemGrid.Rows)
			{
				DisplayStatusItem item = row.DataBoundItem as DisplayStatusItem;
				switch (item.Status)
				{
					case FileStatus.Missing:
						row.DefaultCellStyle.ForeColor = Color.Red;
						break;
					case FileStatus.Modified:
						row.DefaultCellStyle.ForeColor = Color.FromArgb(69, 78, 206);
						break;
					case FileStatus.Added:
						row.DefaultCellStyle.ForeColor = Color.FromArgb(68, 136, 60);
						break;
					case FileStatus.Unknown:
						row.DefaultCellStyle.ForeColor = Color.FromArgb(213, 112, 58);
						break;
					case FileStatus.Removed:
						row.DefaultCellStyle.ForeColor = Color.FromArgb(119, 6, 95);
						break;
				}

				var cell = row.Cells[0];
			}

			UpdateStatusWithCounts();
		} 

		#endregion

		private void UpdateStatusWithCounts()
		{
			UpdateStatus("{0} selected, {1} total", StatusItemGrid.SelectedRows.Count, StatusItemGrid.Rows.Count);
		}

		private IEnumerable<DisplayStatusItem> SelectedItems
		{
			get
			{
				return StatusItemGrid.SelectedRows
					.Cast<DataGridViewRow>()
					.Select(row => row.DataBoundItem as DisplayStatusItem);
			}
		}

		private void OnRefreshClick(object sender, EventArgs e)
		{
			GetStatus();
		}

		#region OnCommitClick

		private void OnCommitClick(object sender, EventArgs e)
		{
			this.StatusItemGrid.EndEdit();
			try
			{
				bool partialCommit = items.Any(i => !i.Checked);
				if (partialCommit && DialogResult.Yes != MessageBox.Show(this, "Are you sure you want to perform a partial commit?", "Partial Commit Warning", MessageBoxButtons.YesNo, MessageBoxIcon.Question, MessageBoxDefaultButton.Button2))
						return;

				if (!IsRepositorySelected())
					return;
				if (String.IsNullOrEmpty(CommitMessage.Text))
				{
					MessageBox.Show(this, "Nothing Committed!\r\n\r\nPlease enter a commit message.", "HgRemote", MessageBoxButtons.OK, MessageBoxIcon.Information, MessageBoxDefaultButton.Button1);
					CommitMessage.Focus();
					return;
				}

				string[] files = null;
				if (partialCommit)
					files = items.Where(i => i.Checked).Select(i => i.Path).ToArray();

				List<StatusItem> newStatuses = Client.Current.Commit(currentRepo.Name, Settings.Current.User, CommitMessage.Text, files);
				Rebind(newStatuses);

				tsUndo.Enabled = true;
				CommitMessage.Text = null;
			}
			catch (Exception x)
			{
				HandleError(x);
			}
		} 

		#endregion

		private bool IsRepositorySelected()
		{
			if (currentRepo == null)
			{
				MessageBox.Show(this, "Select a repository first!");
				return false;
			}
			return true;
		}

		#region OnStatusGridSelectionChanged

		private void OnStatusGridSelectionChanged(object sender, EventArgs e)
		{
			try
			{
				DisplayStatusItem item = SelectedItems.FirstOrDefault();

				DiffView.SuspendLayout();
				DiffView.Clear();

				if (item != null)
				{
					if (item.DiffView == null)
						item.DiffView = Client.Current.Diff(currentRepo.Name, item.Path);

					foreach (string line in item.DiffView)
					{
						if (line != null)
						{
							if (regexDiffHeaders.IsMatch(line))
								DiffView.SelectionColor = Color.FromArgb(69, 78, 206);
							else if (regexDiffAdds.IsMatch(line))
								DiffView.SelectionColor = Color.FromArgb(68, 136, 60);
							else if (regexDiffRemoves.IsMatch(line))
								DiffView.SelectionColor = Color.FromArgb(119, 6, 95);
							else if (regexDiffLineNums.IsMatch(line))
								DiffView.SelectionColor = Color.FromArgb(255, 153, 0);
							else
								DiffView.SelectionColor = Color.Black;
						}
						DiffView.AppendText(line + Environment.NewLine);
					}
				}
				DiffView.ResumeLayout();

				UpdateStatusWithCounts();
			}
			catch (Exception x)
			{
				HandleError(x);
			}
		} 

		#endregion

		#region Status Grid Context Menu Updating

		private void OnDisplayStatusGridContextMenu(object sender, DataGridViewCellContextMenuStripNeededEventArgs e)
		{
			var selectedStatuses = SelectedItems.Select(i => i.Status).Distinct();
			var count = selectedStatuses.Count();

			if (count == 1)
				e.ContextMenuStrip = UpdateContextMenu(selectedStatuses.First());
			else if (count > 1)
				e.ContextMenuStrip = UpdateContextMenu(FileStatus.Invalid);
			else
				e.ContextMenuStrip = null;
		}

		private ContextMenuStrip UpdateContextMenu(FileStatus fs)
		{
			StatusContextMenu.SuspendLayout();

			foreach (ToolStripItem item in StatusContextMenu.Items)
				item.Visible = false;

			const bool t = true;
			const bool f = false;

			UpdateContextMenuItem(fs, ciVisualDiff, t, t, t, t, f);
			//UpdateContextMenuItem(fs, ciEdit, t, t, f, f, t);
			//UpdateContextMenuItem(fs, ciViewMissing, f, f, t, t, f);
			//UpdateContextMenuItem(fs, ciViewOther, t, t, f, f, f);
			//UpdateContextMenuItem(fs, ciSep1, t, t, t, t, t);
			UpdateContextMenuItem(fs, ciRevert, t, t, t, t, f);
			//UpdateContextMenuItem(fs, ciSep2, t, t, t, t, f);
			//UpdateContextMenuItem(fs, ciLog, t, t, t, t, f);
			//UpdateContextMenuItem(fs, ciSep3, t, t, t, f, f);
			UpdateContextMenuItem(fs, ciForget, t, t, t, f, f);
			UpdateContextMenuItem(fs, ciAdd, f, f, f, f, t);
			//UpdateContextMenuItem(fs, ciGuessRename, f, f, t, f, t);
			//UpdateContextMenuItem(fs, ciSep4, t, f, f, f, f);
			//UpdateContextMenuItem(fs, ciCopy, t, f, f, f, f);
			//UpdateContextMenuItem(fs, ciRename, t, f, f, f, f);
			//UpdateContextMenuItem(fs, ciIgnore, f, f, f, f, t);
			UpdateContextMenuItem(fs, ciDeleteUnversioned, f, f, f, f, t);

			ciInvalid.Visible = (fs == FileStatus.Invalid);

			StatusContextMenu.ResumeLayout();
			return StatusContextMenu;
		}

		private void UpdateContextMenuItem(FileStatus fs, ToolStripItem item, bool mod, bool add, bool del, bool rem, bool unknown)
		{
			switch (fs)
			{
				case FileStatus.Modified:
					item.Visible = mod;
					break;
				case FileStatus.Added:
					item.Visible = add;
					break;
				case FileStatus.Missing:
					item.Visible = del;
					break;
				case FileStatus.Removed:
					item.Visible = rem;
					break;
				case FileStatus.Unknown:
					item.Visible = unknown;
					break;
			}
		}

		#endregion

		#region Context Menu Event Handlers

		private void OnContextMenuAdd(object sender, EventArgs e)
		{
			var paths = SelectedItems
				.Where(i => i.Status == FileStatus.Unknown)
				.Select(i => i.Path)
				.ToArray();

			try
			{
				var newstatus = Client.Current.Add(currentRepo.Name, paths);
				Rebind(newstatus);
			}
			catch (Exception x)
			{
				HandleError(x);
			}
		}

		private void OnContextMenuForget(object sender, EventArgs e)
		{
			var paths = SelectedItems
				.Where(i => i.Status == FileStatus.Added || i.Status == FileStatus.Modified || i.Status == FileStatus.Missing)
				.Select(i => i.Path)
				.ToArray();

			try
			{
				var newstatus = Client.Current.Forget(currentRepo.Name, paths);
				Rebind(newstatus);
			}
			catch (Exception x)
			{
				HandleError(x);
			}
		}

		private void OnContextMenuRevert(object sender, EventArgs e)
		{
			RevertDialog rd = new RevertDialog();
			DialogResult dr = rd.ShowDialog(this);

			if (dr == DialogResult.Yes)
			{
				try
				{
					var paths = SelectedItems
						.Where(i => i.Status == FileStatus.Added || i.Status == FileStatus.Modified || i.Status == FileStatus.Missing)
						.Select(i => i.Path)
						.ToArray();

					var newstatus = Client.Current.Revert(currentRepo.Name, rd.BackupChanges, paths);
					Rebind(newstatus);
				}
				catch (Exception x)
				{
					HandleError(x);
				}
			}
		}

		#endregion

		#region OnDeleteUnversionedClick

		private void OnDeleteUnversionedClick(object sender, EventArgs e)
		{
			if (DialogResult.Yes == MessageBox.Show(this, "Are you sure you want to revert these unversioned files?", "Confirm Delete", MessageBoxButtons.YesNo, MessageBoxIcon.Warning, MessageBoxDefaultButton.Button2))
			{
				var paths = SelectedItems
					.Where(i => i.Status == FileStatus.Unknown)
					.Select(i => i.Path)
					.ToArray();

				try
				{
					var newstatus = Client.Current.DeleteUnversioned(currentRepo.Name, paths);
					Rebind(newstatus);
				}
				catch (Exception x)
				{
					HandleError(x);
				}
			}
		} 

		#endregion

		private void StatusItemGrid_CellDoubleClick(object sender, DataGridViewCellEventArgs e)
		{
			DisplayStatusItem item = StatusItemGrid.Rows[e.RowIndex].DataBoundItem as DisplayStatusItem;
			DoVisualDiff(item);
		}

		#region DoVisualDiff

		private void DoVisualDiff(DisplayStatusItem item)
		{
			if (item == null)
				return;

			if (item.Status != FileStatus.Modified && item.Status != FileStatus.Added)
			{
				MessageBox.Show(this, "Nothing to compare.", "HgRemote", MessageBoxButtons.OK, MessageBoxIcon.Information);
				return;
			}

			try
			{
				VisualDiffInfo vdi = Client.Current.VisualDiff(currentRepo.Name, item.Path);

				vdi.TempLeftPath = Path.GetTempFileName();
				vdi.TempRightPath = Path.GetTempFileName();
				string leftName = null;
				if (vdi.Left != null)
				{
					File.WriteAllBytes(vdi.TempLeftPath, vdi.Left);
					leftName = "Working Copy: " + item.Path;
				}
				else
				{
					leftName = "[non-existant]";
				}
				File.WriteAllBytes(vdi.TempRightPath, vdi.Right);

				//"C:\Program Files\WinMerge\WinMergeU.exe" /r /e /x /ub /wl /dl "[non-existant] @4", /dr "f.txt" "c:\users\dave\appdata\local\temp\visualdiff.hbcu9g\empty" "C:\TestHg\RepoA\f.txt"
				WinMerge(vdi, leftName, "Working Copy: " + item.Path);
			}
			catch (Exception x)
			{
				HandleError(x);
			}
		} 

		#endregion

		#region WinMerge

		private void WinMerge(VisualDiffInfo vdi, string leftName, string rightName)
		{
			string winmerge = Environment.ExpandEnvironmentVariables(@"%PROGRAMFILES%\WinMerge\WinMergeU.exe");
			if (!File.Exists(winmerge))
			{
				MessageBox.Show(this, "Install WinMerge!");
				return;
			}

			const string argsFormat = "/r /e /x /ub /wl /dl \"{0}\", /dr \"{1}\" \"{2}\" \"{3}\"";
			string args = String.Format(argsFormat, leftName, rightName, vdi.TempLeftPath, vdi.TempRightPath);

			Process p = new Process();
			p.StartInfo.FileName = winmerge;
			p.StartInfo.Arguments = args;
			p.StartInfo.UseShellExecute = false;
			p.Start();

			vdi.WinMergeProcess = p;

			ParameterizedThreadStart ts = new ParameterizedThreadStart(WatchForWinMergeExit);
			Thread t = new Thread(ts);
			t.Start(vdi);
		} 

		#endregion

		#region WatchForWinMergeExit

		private void WatchForWinMergeExit(object oDiffInfo)
		{
			VisualDiffInfo vdi = oDiffInfo as VisualDiffInfo;
			while (true)
			{
				if (vdi.WinMergeProcess.WaitForExit(500))
				{
					if (File.Exists(vdi.TempLeftPath))
						File.Delete(vdi.TempLeftPath);
					if (File.Exists(vdi.TempRightPath))
						File.Delete(vdi.TempRightPath);
					return;
				}
			}
		} 

		#endregion

		#region OnUndoClick

		private void OnUndoClick(object sender, EventArgs e)
		{
			try
			{
				var newstatus = Client.Current.Rollback(currentRepo.Name);
				Rebind(newstatus);
				tsUndo.Enabled = false;
			}
			catch (Exception x)
			{
				HandleError(x);
			}
		} 

		#endregion

		private void OnVisualDiffClick(object sender, EventArgs e)
		{
			DoVisualDiff(SelectedItems.FirstOrDefault());
		}

		#region OnAppWinClosing

		private void OnAppWinClosing(object sender, FormClosingEventArgs e)
		{
			Settings s = Settings.Current;
			if (currentRepo != null)
				s.SelectedRepo = currentRepo.Name;
			s.WindowState = this.WindowState;
			s.WindowSize = this.Size;
			s.WindowLocation = this.Location;
			s.HorizontalSplitDistance = SplitCommit.SplitterDistance;
			s.VerticalSplitDistance = SplitStatus.SplitterDistance;
			s.SaveChanges();
		} 

		#endregion

		#region UpdateStatus

		private void UpdateStatus(string format, params object[] args)
		{
			string status = String.Format(format, args);
			Action action = () => StatusLabel.Text = status;
			if (this.InvokeRequired)
				this.Invoke(action);
			else
				action();
		} 

		#endregion

		#region OnSettingsClick

		private void OnSettingsClick(object sender, EventArgs e)
		{
			SettingsDialog sd = new SettingsDialog();
			sd.ShowDialog(this);
		}

		#endregion

		private void miIncoming_Click(object sender, EventArgs e)
		{
			CmdResultDialog sd = new CmdResultDialog();
			Client.SetResponseTarget(sd);
			Client.Current.Incoming(currentRepo.Name);
			sd.ShowDialog(this);
		}

		private void miOutgoing_Click(object sender, EventArgs e)
		{
			CmdResultDialog sd = new CmdResultDialog();
			Client.SetResponseTarget(sd);
			Client.Current.Outgoing(currentRepo.Name);
			sd.ShowDialog(this);
		}

		private void miPull_Click(object sender, EventArgs e)
		{
			CmdResultDialog sd = new CmdResultDialog();
			Client.SetResponseTarget(sd);
			Client.Current.Fetch(currentRepo.Name, Settings.Current.User);
			sd.ShowDialog(this);
			GetStatus();
		}

		private void miPush_Click(object sender, EventArgs e)
		{
			CmdResultDialog sd = new CmdResultDialog();
			Client.SetResponseTarget(sd);
			Client.Current.Push(currentRepo.Name);
			sd.ShowDialog(this);
			GetStatus();
		}

		private void Tools_Layout(object sender, LayoutEventArgs e)
		{
			RepositorySelect.Width = Tools.Width - 410;
		}

	}
}
